import socket
import json
import threading
import random
import time

config = json.loads(open('./config.json','r').read())

locks = threading.Lock()

body_db=[]
boarddb = []
points_db = []

for i in range(config['rules']['maxplayer']):
    body_db.append({'active':0})

nowid = 0

def inzone(inx,iny):
    return (inx>=0) and (inx<config['map']['maxx']) and (iny>=0) and (iny<config['map']['maxy']) 

def get_json(sub_sk):
    t = ''
    while t == '':
        t = str(sub_sk.recv(3),encoding='utf-8')
    len = int(t,16)
    rec = json.loads(str(sub_sk.recv(len),encoding='utf-8'))
    return rec

def send_json(sub_sk,jsons):
    jsons_str = json.dumps(jsons)
    sub_sk.send(bytes("%03x"%len(bytes(jsons_str,encoding='utf-8')),encoding='utf-8'))
    sub_sk.send(bytes(jsons_str,encoding='utf-8'))

def summonboard():
    '''/summnon xrcxjcw:pysnake2:board ~ ~ ~ {'nbt':'(doge'}'''
    locks.acquire()
    global boarddb,body_db,config
    boarddb = []
    for i in range(config['map']['maxx']):
        tp = []
        for j in range(config['map']['maxy']):
            tp.append(-1)
        boarddb.append(tp)
    
    for i in range(len(body_db)):
        if body_db[i]['active'] == 0:
            continue

        if body_db[i]['headxy'][0] < 0 or body_db[i]['headxy'][0] >= config['map']['maxx'] \
            or body_db[i]['headxy'][1] < 0 or body_db[i]['headxy'][1] >= config['map']['maxy']:
            locks.release()
            killid(i)
            locks.acquire()
        else:
            boarddb[body_db[i]['headxy'][0]][body_db[i]['headxy'][1]] = i
            for dat in body_db[i]['bodydat']:
                boarddb[dat[0]][dat[1]] = i
                
    for i in range(len(points_db)):
        boarddb[points_db[i][0]][points_db[i][1]] = -3
    
    locks.release()

def freshboard():
    locks.acquire()
    global body_db
    for i in range(len(body_db)):
        if body_db[i]['active'] == 0:
            continue
        if body_db[i]['fx']==1:
            body_db[i]['headxy'][1]+=1
        if body_db[i]['fx']==2:
            body_db[i]['headxy'][0]+=1
        if body_db[i]['fx']==3:
            body_db[i]['headxy'][1]-=1
        if body_db[i]['fx']==4:
            body_db[i]['headxy'][0]-=1
        body_db[i]['bodydat'].append([body_db[i]['headxy'][0],body_db[i]['headxy'][1]])
        body_db[i]['bodydat'] = body_db[i]['bodydat'][-body_db[i]['len']:]
    locks.release()
    
def killid(id):
    '''kill id input && mark on boarddb'''
    locks.acquire()
    body_db[id]['active']  = 0
    for nowi in range(len(body_db[id]['bodydat'])):
        try:
            if inzone(body_db[id]['bodydat'][nowi][0],body_db[id]['bodydat'][nowi][1]):
                boarddb[body_db[id]['bodydat'][nowi][0]][body_db[id]['bodydat'][nowi][1]] = -3
                points_db.append([body_db[id]['bodydat'][nowi][0],body_db[id]['bodydat'][nowi][1]])
        except IndexError:
            pass
    locks.release()
    
def handler():
    locks.acquire()
    for nowi in range(len(body_db)):
        if body_db[nowi]['active'] == 0:
            continue
        if boarddb[body_db[nowi]['headxy'][0]][body_db[nowi]['headxy'][1]] == -3:
            points_db.remove([body_db[nowi]['headxy'][0],body_db[nowi]['headxy'][1]])
            body_db[nowi]['len'] += 1
            boarddb[body_db[nowi]['headxy'][0]][body_db[nowi]['headxy'][1]] = nowi
        elif boarddb[body_db[nowi]['headxy'][0]][body_db[nowi]['headxy'][1]] != nowi:
            locks.release()
            killid(nowi)
            locks.acquire()
    locks.release()

def summonboard_da():
    while True:
        freshboard()
        summonboard()
        handler()
        time.sleep(0.5)

def cutboard(snakeid):
    locks.acquire()
    global boarddb,body_db
    res = []
    for i in range(body_db[snakeid]['headxy'][0]-config['player']['see']['l'],body_db[snakeid]['headxy'][0]+config['player']['see']['r']):
        tp = []
        for j in range(body_db[snakeid]['headxy'][1]-config['player']['see']['u'],body_db[snakeid]['headxy'][1]+config['player']['see']['d']):
            try:
                if i<0 or j<0:
                    tp.append(-2)
                else:
                    tp.append(boarddb[i][j])
            except IndexError:
                tp.append(-2)
        res.append(tp)
    locks.release()
    return res

def sub_process(sock):
    try:
        global nowid,body_db
        locks.acquire()
        nowid = -1
        for nowi in range(len(body_db)):
            if body_db[nowi]['active'] == 0:
                body_db[nowi] = {
                    'headxy':[random.randint(0,config['map']['maxx']),random.randint(0,config['map']['maxy'])],
                    'bodydat':[],
                    'fx':random.randint(1,4),
                    'active':1,
                    'len':config['rules']['initlen']
                }
                nowid = nowi
                break
        print('set nowi=',nowi)

        if nowi == -1 :
            sock.close()

        locks.release()
        send_json(sock,{
            "see":config['player']['see']
        })
        while True :
            if body_db[nowi]['active'] == 0:
                send_json(sock,{})
            js = get_json(sock)
            if js['t'] == 'g':
                send_json(sock,{'res':cutboard(nowi)})
            if js['t'] == 'u':
                body_db[nowi]['fx'] = 3
            if js['t'] == 'd':
                body_db[nowi]['fx'] = 1
            if js['t'] == 'l':
                body_db[nowi]['fx'] = 4
            if js['t'] == 'r':
                body_db[nowi]['fx'] = 2
        
    except ConnectionResetError:
        body_db[nowi]['active'] = 0
        print('disconnected')
        pass
    
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('',8889))
sk.listen(1024)

r = threading.Thread(target=summonboard_da)
r.setDaemon(1)
r.start()

while True:
    s,a = sk.accept()
    t = threading.Thread(target=sub_process,args=(s,))
    t.setDaemon(1)
    t.start()